package com.seezoon.infrastructure.tcp.codec;

import com.seezoon.infrastructure.error.ErrorCode;
import com.seezoon.infrastructure.exception.Assertion;
import com.seezoon.infrastructure.exception.ExceptionFactory;
import com.seezoon.infrastructure.tcp.common.Encoding;
import com.seezoon.infrastructure.tcp.common.MessageConstants;
import com.seezoon.infrastructure.tcp.common.MessageField;
import com.seezoon.infrastructure.utils.BcdUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

@Slf4j
public class JTSerialization {

    /**
     * 编码
     *
     * @param body
     * @param <T>
     * @return
     */
    public static <T> byte[] encode(T body) {
        Assertion.notNull(body, "body must not be null");
        ByteBuf buffer = Unpooled.buffer(1024);
        final Class<?> clazz = body.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            ReflectionUtils.makeAccessible(field);
            Object value = ReflectionUtils.getField(field, body);
            final Class<?> type = field.getType();

            // short 对无符号byte
            if (type.isAssignableFrom(short.class)) {
                buffer.writeByte(null != value ? (Short) value : 0);
                continue;
            }
            // int 对无符号word
            if (type.isAssignableFrom(int.class)) {
                buffer.writeShort(null != value ? (Integer) value : 0);
                continue;
            }
            // long 对无符号dword
            if (type.isAssignableFrom(long.class)) {
                buffer.writeInt(null != value ? ((Long) value).intValue() : 0);
                continue;
            }
            // 字节数组
            if (type.isAssignableFrom(byte[].class)) {
                buffer.writeBytes((byte[]) value);
                continue;
            }
            // int 数组
            if (type.isAssignableFrom(int[].class)) {
                if (null == value) {
                    continue;
                }
                int[] array = (int[]) value;
                for (int i : array) {
                    buffer.writeShort(i);
                }
                continue;
            }
            // String
            if (type.isAssignableFrom(String.class)) {
                final String str = StringUtils.trimToEmpty((String) value);
                MessageField annotation = Objects.requireNonNull(field.getAnnotation(MessageField.class),
                        "string data field must has DataField annotation");
                final int length = annotation.length();
                Encoding encoding = Objects.requireNonNull(annotation.encoding(),
                        "encoding data field must has encoding annotation");
                switch (encoding) {
                    case UTF8 -> {
                        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
                        Assert.isTrue(bytes.length <= length,
                                field.getName() + " encoding data field > annotation length");
                        buffer.writeBytes(bytes);
                        // 不足的部0
                        int remain = length - bytes.length;
                        for (int i = 0; i < remain; i++) {
                            buffer.writeByte(0);
                        }
                    }
                    case BCD -> {
                        byte[] bytes = BcdUtils.stringToBcd(str);
                        Assert.isTrue(bytes.length == length,
                                field.getName() + " encoding data field must equal length");
                        buffer.writeBytes(bytes);
                    }
                    case GBK -> {
                        // 按协议要求GBK为空不写，一般是最后一位
                        if (StringUtils.isEmpty(str)) {
                            continue;
                        }
                        byte[] bytes = str.getBytes(MessageConstants.GBK);
                        buffer.writeBytes(bytes);
                    }
                }
                continue;
            }
            log.error("unsupported encode field {}", field.getName());
            throw new UnsupportedOperationException();
        }
        int bytesWritten = buffer.writerIndex();
        byte[] result = new byte[bytesWritten];
        buffer.getBytes(0, result);
        return result;
    }


    public static <T> T decode(ByteBuf buffer, Class<T> clazz) {
        Assertion.notNull(clazz, "clazz must not be null");
        try {
            T instance = clazz.getDeclaredConstructor().newInstance();
            if (null == buffer) {
                return instance;
            }
            Field[] declaredFields = clazz.getDeclaredFields();

            for (Field field : declaredFields) {
                ReflectionUtils.makeAccessible(field);
                final Class<?> type = field.getType();

                // short 对无符号byte
                if (type.isAssignableFrom(short.class)) {
                    short value = buffer.readUnsignedByte();
                    ReflectionUtils.setField(field, instance, value);
                    continue;
                }

                // int 对无符号word
                if (type.isAssignableFrom(int.class)) {
                    int value = buffer.readUnsignedShort();
                    ReflectionUtils.setField(field, instance, value);
                    continue;
                }

                // long 对无符号dword
                if (type.isAssignableFrom(long.class)) {
                    long value = buffer.readUnsignedInt();
                    ReflectionUtils.setField(field, instance, value);
                    continue;
                }

                // 字节数组
                if (type.isAssignableFrom(byte[].class)) {
                    MessageField annotation = Objects.requireNonNull(field.getAnnotation(MessageField.class));
                    int length = annotation.length();
                    String ref = annotation.ref();
                    // 针对byte[4 * n]
                    if (StringUtils.isNotEmpty(ref)) {
                        Field refFiled = ReflectionUtils.findField(clazz, ref);
                        int refValue = (int) ReflectionUtils.getField(refFiled, instance);
                        length = length * refValue;
                    }
                    // 剩余全部放入
                    if (length == 0) {
                        length = buffer.readableBytes();
                    }
                    byte[] bytes = new byte[length];
                    buffer.readBytes(bytes);
                    ReflectionUtils.setField(field, instance, bytes);
                    continue;
                }

                // int数组
                if (type.isAssignableFrom(int[].class)) {
                    MessageField annotation = Objects.requireNonNull(field.getAnnotation(MessageField.class));
                    int length = annotation.length();
                    String ref = annotation.ref();
                    if (StringUtils.isNotEmpty(ref)) {
                        Field refFiled = ReflectionUtils.findField(clazz, ref);
                        int refValue = (int) ReflectionUtils.getField(refFiled, instance);
                        length = length * refValue;
                    }
                    int[] ints = new int[length];
                    for (int i = 0; i < length; i++) {
                        ints[i] = buffer.readUnsignedShort();
                    }
                    ReflectionUtils.setField(field, instance, ints);
                    continue;
                }

                // String
                if (type.isAssignableFrom(String.class)) {
                    MessageField annotation = Objects.requireNonNull(field.getAnnotation(MessageField.class),
                            "string data field must has MessageField annotation");
                    Encoding encoding = Objects.requireNonNull(annotation.encoding(),
                            "encoding data field must has encoding annotation");

                    switch (encoding) {
                        case UTF8 -> {
                            final int length = annotation.length();
                            byte[] bytes = new byte[length];
                            buffer.readBytes(bytes);
                            String str = new String(bytes, StandardCharsets.UTF_8);
                            // 去除多余的补0
                            ReflectionUtils.setField(field, instance, StringUtils.trim(str));
                        }
                        case BCD -> {
                            final int length = annotation.length();
                            byte[] bcdBytes = new byte[length];
                            buffer.readBytes(bcdBytes);
                            String str = BcdUtils.bcdToStringTrimLeadingZero(bcdBytes);
                            ReflectionUtils.setField(field, instance, str);
                        }
                        case GBK -> {
                            // 一般最后一个字符串 可以缺省
                            if (!buffer.isReadable()) {
                                continue;
                            }
                            // 要么是最后一组数据，要么依赖其他字段得到长度
                            int length = 0;
                            String ref = annotation.ref();
                            if (StringUtils.isNotEmpty(ref)) {
                                Field refFiled = ReflectionUtils.findField(clazz, ref);
                                int refValue = (int) ReflectionUtils.getField(refFiled, instance);
                                length = refValue;
                            }
                            if (length == 0) {
                                length = buffer.readableBytes();
                            }
                            byte[] strBytes = new byte[length];
                            buffer.readBytes(strBytes);
                            String str = new String(strBytes, MessageConstants.GBK);
                            ReflectionUtils.setField(field, instance, str);
                        }
                    }
                    continue;
                }
                throw new UnsupportedOperationException("Unsupported field type: " + type.getName());
            }

            return instance;
        } catch (Exception e) {
            log.error("Failed to decode object", e);
            throw ExceptionFactory.bizException(ErrorCode.TCP_MESSAGE_SERIALIZATION_ERROR);
        }
    }
}
